"""
Data models (dataclasses) used across the project.

This module intentionally does NOT depend on any geometry libraries beyond NumPy,
so it can be imported in lightweight contexts (e.g., configuration parsing).
"""

from __future__ import annotations
from dataclasses import dataclass
from typing import Callable, Optional, Tuple
import numpy as np
import math

Vec3 = np.ndarray  # alias for clarity


def unit(v: Vec3) -> Vec3:
    """Return a unit-norm vector. If ||v|| == 0, returns a zero vector of same shape."""
    v = np.asarray(v, dtype=float)
    n = np.linalg.norm(v)
    return v if n == 0.0 else v / n


@dataclass
class Missile:
    """
    Missile flies in a straight line at constant speed, aiming toward 'aim_to' point.

    Attributes
    ----------
    M0 : tuple[float, float, float]
        Initial missile position at t=0 (s).
    speed : float
        Constant speed (m/s).
    aim_to : tuple[float, float, float]
        Fixed aiming point (e.g., the fake target origin). The direction is computed as (aim_to - M0).
    """
    M0: Tuple[float, float, float]
    speed: float
    aim_to: Tuple[float, float, float]

    def pos(self, t: float) -> Vec3:
        """Position at time t (s)."""
        d = unit(np.array(self.aim_to, float) - np.array(self.M0, float))
        return np.array(self.M0, float) + self.speed * d * t


@dataclass
class UAV:
    """
    UAV cruises at constant altitude and constant horizontal speed, toward the origin (0,0).

    This matches problem statement "heading toward fake target direction". If you need
    arbitrary headings, replace the 'dir_xy' computation accordingly.
    """
    R0_xy: Tuple[float, float]
    z: float
    speed: float

    def xy(self, t: float) -> Vec3:
        """Horizontal position (x, y) at time t (s)."""
        origin = np.array([0.0, 0.0], dtype=float)
        R0 = np.array(self.R0_xy, dtype=float)
        dir_xy = unit(origin - R0)
        if np.linalg.norm(dir_xy) == 0.0:
            dir_xy = np.array([-1.0, 0.0], dtype=float)  # fallback to -x if already at origin
        return R0 + self.speed * dir_xy * t


@dataclass
class UAVWithHeading:
    """
    UAV model with fixed heading (in degrees) and constant horizontal speed.
    Provides the same interface needed by DropPlan: xy(t) -> (x,y) at time t.
    """
    R0_xy: Tuple[float, float]
    z: float
    speed: float
    heading_deg: float  # 0° along +x, counter-clockwise

    def xy(self, t: float) -> np.ndarray:
        phi = math.radians(self.heading_deg)
        dir_xy = np.array([math.cos(phi), math.sin(phi)], dtype=float)
        R0 = np.array(self.R0_xy, dtype=float)
        return R0 + self.speed * dir_xy * t

@dataclass
class CloudModel:
    """
    Cloud (smoke) model after detonation.

    r : radius (m) of the spherical cloud with effective concentration
    sink : vertical sink speed (m/s) AFTER detonation
    life : effective lifetime of the cloud (s)
    g : gravitational acceleration (m/s^2), used for vertical drop BEFORE detonation
    """
    r: float = 10.0
    sink: float = 3.0
    life: float = 20.0
    g: float = 9.81


@dataclass
class CylinderTarget:
    """
    The protected target is a vertical circular cylinder.

    base_center : (x, y, z) of the bottom disk center
    radius : radius (m)
    height : height (m)

    For convenience, we also expose commonly used representative points.
    """
    base_center: Tuple[float, float, float]
    radius: float
    height: float

    @property
    def center_point(self) -> Vec3:
        """Geometric center of the cylinder volume."""
        x, y, z0 = self.base_center
        return np.array([x, y, z0 + 0.5 * self.height], dtype=float)

    @property
    def top_center(self) -> Vec3:
        x, y, z0 = self.base_center
        return np.array([x, y, z0 + self.height], dtype=float)

    @property
    def bottom_center(self) -> Vec3:
        x, y, z0 = self.base_center
        return np.array([x, y, z0], dtype=float)


@dataclass
class DropPlan:
    """
    A drop plan ties one UAV with a release and fuse delay.
    It computes the detonation instant and cloud center trajectory after detonation.

    t_rel : release time (s) after receiving the mission
    tau : fuse delay (s) -> detonation at t_det = t_rel + tau
    """
    uav: UAV
    t_rel: float
    tau: float

    # Derived (populated by compute_detonation)
    t_det: Optional[float] = None
    xy_det: Optional[Tuple[float, float]] = None
    z_det: Optional[float] = None

    def compute_detonation(self, model: CloudModel) -> Tuple[float,float,float]:
        """Compute detonation time and center coordinates at detonation."""
        self.t_det = float(self.t_rel + self.tau)
        x, y = self.uav.xy(self.t_det)
        self.xy_det = (float(x), float(y))
        self.z_det = float(self.uav.z - 0.5 * model.g * (self.tau ** 2))
        return x, y, self.z_det


    def center_func(self, model: CloudModel) -> Callable[[float], Optional[Vec3]]:
        """
        Return a function C(t) giving the cloud center at time t, or None if cloud is ineffective.

        Valid only for t in [t_det, t_det + life]. The center sinks down at 'sink' m/s after detonation.
        """
        assert self.t_det is not None and self.xy_det is not None and self.z_det is not None, \
            "Call compute_detonation(model) before requesting center_func"

        def _C(t: float) -> Optional[Vec3]:
            if t < self.t_det or t > self.t_det + model.life:
                return None
            x, y = self.xy_det
            z = self.z_det - model.sink * (t - self.t_det)
            return np.array([x, y, z], dtype=float)

        return _C